/*
 * Copyright (c) 2012 Sean Connelly
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var token = require('./token.js');
var ratio = require('./ratio.js');
var err = require('./err.js');

//
// convert a number string into a ratio token (or an error token, if invalid)
//

exports.lex = function(marker, ratioStrOrig) { // always returns a single token
		var ratioStr = ratioStrOrig;
		// remove trailing `j` for imaginary numbers
		var imag = false;
		if (ratioStr.substr(-1) == 'j')
		{
			imag = true;
			ratioStr = ratioStr.substr(0, ratioStr.length - 1);
		}

		// note: do not need to check for sign of number, because that will be detected as a
		//       unary operation by the parser

		// check for special values
		if ((new RegExp('^0_*NaN_*$')).test(ratioStr)) // not a number
			return token.Ratio(marker, ratio.createNaN(), imag);
		else if ((new RegExp('^0_*inf_*$')).test(ratioStr)) // infinity
			return token.Ratio(marker, ratio.createInf(), imag);

		// convert to lower case and detect base
		var prefix = ratioStr.substr(0, 2);
		ratioStr = ratioStr.toLowerCase();
		var base_str = '0123456789';
		var exp_str = 'p';
		var exp_base = 2;
		if (prefix == '0b') // binary
		{
			ratioStr = ratioStr.substr(2);
			base_str = '01';
		}
		else if (prefix == '0c') // octal
		{
			ratioStr = ratioStr.substr(2);
			base_str = '01234567';
		}
		else if (prefix == '0x') // hexidecimal
		{
			ratioStr = ratioStr.substr(2);
			base_str = '0123456789abcdef';
		}
		else // it's decimal
		{
			exp_str = 'e';
			exp_base = 10;
		}

		// parse number components based on base_str
		var numc = ratioStr.match(new RegExp('^' +
			'([' + base_str + ']*)' + // numc[1] = prior to decimal
			'(?:\\.' +
				'([' + base_str + ']*))?' + // numc[2] = after decimal
			'(?:(?:' + exp_str.toLowerCase() + '|' + exp_str.toUpperCase() + ')' +
				'((?:\\+|\\-)?[' + base_str + ']+)' + // numc[3] = exponent (including sign)
			')?' +
			'$'));
		if (numc === null || (numc[1] == '' && (numc[2] == '' || typeof numc[2] == 'undefined')))
			return token.Error(marker, err.InvalidNumber(ratioStrOrig));

		// check for leading zeros
		if (base_str.length == 10 && numc[1].length > 1 && numc[1].substr(0, 1) == '0')
			return token.Error(marker, err.InvalidLeadingZero(ratioStrOrig));

		// create ratio based on components
		if (numc[1] == '')
			numc[1] = '0';
		if (numc[2] == '' || typeof numc[2] == 'undefined')
			numc[2] = '0';
		var ratio_val = ratio.createBase(numc[1] + '.' + numc[2], base_str.length);
		if (typeof numc[3] != 'undefined' && numc[3] != '')
		{
			// val = val * exp_base ^ exponent
			ratio_val.mul(ratio.createInt(exp_base).pow(ratio.createDec(numc[3])));
		}
		return token.Ratio(marker, ratio_val, imag);
	};
